package com.scnlf.scnlf.gateway.config.loadbalance;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import com.scnlf.scnlf.common.api.dto.CustomRouteParam;
import com.scnlf.scnlf.gateway.config.CustomRouteConfig;
import com.scnlf.scnlf.gateway.enums.LoadBalancerTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定义 SpringCloud 负载均衡算法
 * 负载均衡算法的默认实现是 {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer}
 *
 */
@Slf4j
public class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final String serviceId;

    private final AtomicInteger position;

    private final LoadBalancerTypeEnum type;

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    private final CustomRouteConfig routeConfig;

    public CustomSpringCloudLoadBalancer(String serviceId, LoadBalancerTypeEnum type,
                                         ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, CustomRouteConfig routeConfig) {
        this(serviceId, new Random().nextInt(1000), type, serviceInstanceListSupplierProvider, routeConfig);
    }

    public CustomSpringCloudLoadBalancer(String serviceId, int seedPosition, LoadBalancerTypeEnum type,
                                         ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, CustomRouteConfig routeConfig) {
        this.serviceId = serviceId;
        this.position = new AtomicInteger(seedPosition);
        this.type = type;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.routeConfig = routeConfig;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(request, supplier, serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(Request request,
                                                              ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(request, serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(Request request, List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }

        if (Objects.equals(type, LoadBalancerTypeEnum.ROUND_ROBIN)){
            return this.getRoundRobinInstance(instances);
        }else if (Objects.equals(type, LoadBalancerTypeEnum.RANDOM)){
            return this.getRandomInstance(instances);
        }else if (Objects.equals(type, LoadBalancerTypeEnum.DEV)){
            return this.getDevelopmentInstance(instances);
        }else if (Objects.equals(type, LoadBalancerTypeEnum.GATEWAY)){
            return this.getGatewayDevelopmentInstance(request, instances);
        }
        return this.getRoundRobinInstance(instances);
    }

    /**
     * 网关处理请求
     * @param request
     * @param instances
     * @return
     */
    private Response<ServiceInstance> getGatewayDevelopmentInstance(Request request, List<ServiceInstance> instances) {
        // 把 request转为默认的DefaultRequest，从request中拿到请求的 header 信息，进行自定义负载均衡处理
        DefaultRequest<RequestDataContext> defaultRequest = Convert.convert(new TypeReference<DefaultRequest<RequestDataContext>>() {}, request);
        RequestDataContext context = defaultRequest.getContext();
        RequestData clientRequest = context.getClientRequest();
        HttpHeaders headers = clientRequest.getHeaders();
        List<String> routeKeyList = headers.get("route-key");
        String routeKey = CollectionUtil.isEmpty(routeKeyList) ? null : routeKeyList.get(0);
        if(Objects.isNull(routeKey) || routeKey.equals("")){
            return getRoundRobinInstance(instances);
        }
        String serviceId = instances.get(0).getServiceId();
        List<CustomRouteParam> customRouteParams = routeConfig.getRouteConfig(serviceId);
        if(CollectionUtil.isEmpty(customRouteParams)){
            // 未做定制化配置，则按照轮询策略处理
            return getRoundRobinInstance(instances);
        }
        int pos = Math.abs(routeKey.hashCode() % instances.size());
        CustomRouteParam proxyRouteConfig = customRouteParams.get(pos);
        for(ServiceInstance instance : instances){
            if(Objects.equals(instance.getHost(), proxyRouteConfig.getHost()) &&
                Objects.equals(instance.getPort(), proxyRouteConfig.getPort())){
                return new DefaultResponse(instance);
            }
        }
        throw new RuntimeException("not found custom proxy instance");
    }

    /**
     * 开发环境处理请求，可以拓展使用 header 自定义 forward-to 配置本地 IP，让本地 IP
     * @param instances
     * @return
     */
    private Response<ServiceInstance> getDevelopmentInstance(List<ServiceInstance> instances) {
        return getRoundRobinInstance(instances);
    }

    /**
     * 随机算法处理请求
     * @param instances
     * @return
     */
    private Response<ServiceInstance> getRandomInstance(List<ServiceInstance> instances) {
        int index = ThreadLocalRandom.current().nextInt(instances.size());
        ServiceInstance instance = instances.get(index);
        return new DefaultResponse(instance);
    }

    /**
     * 轮询处理请求
     * @param instances
     * @return
     */
    private Response<ServiceInstance> getRoundRobinInstance(List<ServiceInstance> instances) {
        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }

}
